/****************************************************************************
**
** Copyright (C) TERIFLIX Entertainment Spaces Pvt. Ltd. Bengaluru
** Author: Prashanth N Udupa (prashanth.udupa@teriflix.com)
**
** This code is distributed under GPL v3. Complete text of the license
** can be found here: https://www.gnu.org/licenses/gpl-3.0.txt
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

#include <QtCore>

/**
 * On Windows we we create installer packages, we need to ensure that all the files
 * selected by windeployqt are bundled into the installer. Everytime we add a Qt
 * module, some extra DLLs, EXEs and sometimes even resources are selected by windeployqt.
 * We have to make sure that we bundle all of them into the installer.
 *
 * Create a NSIS file from scratch each time is a rather difficult job. fillnsi.exe comes
 * to the rescue.
 *
 * windeployqt can be executed with --list relative option. This will cause windeployqt
 * to relative paths of all files it copies into the deploy directory.
 *
 * This program then parses that list and generates the necessary Install and Unintall
 * NSIS lines.
 *
 * NOTE: Most developers will never have to build this program ever. We at TERIFLIX build
 * and use this program only when we have to create updated .nsi scripts from their .nsi.in
 * counterparts.
 */
int main(int argc, char **argv)
{
    QCoreApplication a(argc, argv);

    QCommandLineParser parser;

    QCommandLineOption listFileOption("list", "File list generated by windeployqt when used with --list relative option", "list-file");
    parser.addOption(listFileOption);

    QCommandLineOption installsKeyOption("installs-key", "Default is WINDEPLOYQT_INSTALLS", "installs-key");
    parser.addOption(installsKeyOption);

    QCommandLineOption uninstallsKeyOption("uninstalls-key", "Default is WINDEPLOYQT_UNINSTALLS", "uninstalls-key");
    parser.addOption(uninstallsKeyOption);

    QCommandLineOption inputFileOption("input", "Name of the .nsi.in file to read", "input-file");
    parser.addOption(inputFileOption);

    QCommandLineOption outputFileOption("output", "Name of the .nsi file to write", "output-file");
    parser.addOption(outputFileOption);

    parser.addHelpOption();

    parser.process(a);

    if(!parser.isSet(listFileOption) || !parser.isSet(installsKeyOption) || !parser.isSet(uninstallsKeyOption))
    {
        parser.showHelp(-1);
        return -1;
    }

    const QString keyPrefix = QStringLiteral("$$");
    const QString installsKey = keyPrefix + (parser.isSet(installsKeyOption) ? parser.value(installsKeyOption) : QStringLiteral("WINDEPLOYQT_INSTALLS"));
    const QString uninstallsKey = keyPrefix + (parser.isSet(uninstallsKeyOption) ? parser.value(uninstallsKeyOption) : QStringLiteral("WINDEPLOYQT_UNINSTALLS"));
    if(installsKey == keyPrefix)
    {
        qWarning("Invalid installs key supplied.");
        return -1;
    }

    if(uninstallsKey == keyPrefix)
    {
        qWarning("Invalid uninstalls key supplied.");
        return -1;
    }

    qInfo("Using Installs Key as %s", qPrintable(installsKey));
    qInfo("Using Uninstalls Key as %s", qPrintable(uninstallsKey));

    const QString listFileName = parser.value(listFileOption);
    QFile listFile(listFileName);
    if( !listFile.open(QFile::ReadOnly) )
    {
        qWarning("Cannot open file '%s' for reading.", qPrintable(listFileName));
        return -1;
    }

    const QString inputFileName = parser.value(inputFileOption);
    QFile inputFile(inputFileName);
    if( !inputFile.open(QFile::ReadOnly) )
    {
        qWarning("Cannot open file '%s' for reading.", qPrintable(inputFileName));
        return -1;
    }

    const QString outputFileName = parser.value(outputFileOption);
    QFile outputFile(outputFileName);
    if( !outputFile.open(QFile::WriteOnly) )
    {
        qWarning("Cannot open file '%s' for writing.", qPrintable(outputFileName));
        return -1;
    }

    QString nsisScript = inputFile.readAll();

    // There should be exactly one installs and uninstalls key
    const int nrInstallsKey = nsisScript.count(installsKey);
    const int nrUninstallsKey = nsisScript.count(uninstallsKey);
    if(nrInstallsKey == 0)
    {
        qWarning("'%s' was not found in '%s'.", qPrintable(installsKey), qPrintable(inputFileName));
        return -1;
    }

    if(nrInstallsKey > 1)
    {
        qWarning("More than one '%s' found in '%s'.", qPrintable(installsKey), qPrintable(inputFileName));
        return -1;
    }

    if(nrUninstallsKey == 0)
    {
        qWarning("'%s' was not found in '%s'.", qPrintable(uninstallsKey), qPrintable(inputFileName));
        return -1;
    }

    if(nrUninstallsKey > 1)
    {
        qWarning("More than one '%s' found in '%s'.", qPrintable(uninstallsKey), qPrintable(inputFileName));
        return -1;
    }

    const QStringList fileList = QString::fromLatin1(listFile.readAll()).split("\r\n", QString::SkipEmptyParts);
    if(fileList.isEmpty())
    {
        qWarning("No files were listed in '%s'.", qPrintable(listFileName));
        return -1;
    }

    const QFileInfo outputFileInfo(outputFileName);
    const QDir outputFileDir = outputFileInfo.absoluteDir();

    QString installStr, uninstallStr;
    QTextStream its(&installStr, QIODevice::WriteOnly);
    QTextStream uts(&uninstallStr, QIODevice::WriteOnly);
    QStringList dirs;
    QString outdir;

    Q_FOREACH(QString file, fileList)
    {
        file = file.trimmed();

        QFileInfo fi( outputFileDir.absoluteFilePath(file) );
        const QString fullPath = fi.absoluteFilePath();
        if( !fi.exists() )
        {
            qWarning("Cannot find path '%s' listed in the '%s'. Skipping.", qPrintable(fullPath), qPrintable(listFileName));
            continue;
        }

        if( fi.suffix() == QStringLiteral("qmlc") )
        {
            qWarning("Skipping '%s'", qPrintable(fullPath));
            continue;
        }

        const int lastSlashIndex = file.lastIndexOf("\\");

        QString dir = lastSlashIndex > 0 ? file.left( lastSlashIndex ) : QString();
        if(outdir != dir)
        {
            if(dir.isEmpty())
                its << "  SetOutPath \"$INSTDIR\"\n";
            else
                its << "  SetOutPath \"$INSTDIR\\" << dir << "\"\n";
            outdir = dir;
        }

        const int si = file.indexOf("\\");
        dir = si > 0 ? file.left(si) : QString();
        if( !dir.isEmpty() && !dirs.contains(dir) )
            dirs.append(dir);

        if(file.isEmpty())
            continue;

        its << "  File \"" << file << "\"\n";
        uts << "  Delete \"$INSTDIR\\" << file << "\"\n";
    }

    Q_FOREACH(QString dir, dirs)
        uts << "  RMDir /r \"$INSTDIR\\" << dir << "\"\n";

    its.flush();
    uts.flush();

    nsisScript.replace(installsKey, installStr);
    nsisScript.replace(uninstallsKey, uninstallStr);

    outputFile.write(nsisScript.toLatin1());

    qInfo("Output written into '%s'", qPrintable(outputFileName));

    return 0;
}
